/*
  flamingo - 2D Game Engine
  Copyright (C) 2009 Bradley Zeis

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

  Bradley Zeis
  flamingoengine@gmail.com
*/

#define FLOPS       1
#define FLCOLOR     1

#include "flamingo.h"
#include "flcolor.h"

PyTypeObject __declspec(dllexport) Color_Type;

Color *uNewColor(double r, double g, double b, double a) {
    return (Color *)PyObject_CallFunction((PyObject *)&Color_Type, "(dddd)", r, g, b, a);
}

void Color_SetBounds(Color *self) {
    if (self->r > 1) { self->r = 1.0; }
    if (self->r < 0) { self->r = 0.0; }
    if (self->g > 1) { self->g = 1.0; }
    if (self->g < 0) { self->g = 0.0; }
    if (self->b > 1) { self->b = 1.0; }
    if (self->b < 0) { self->b = 0.0; }
    if (self->a > 1) { self->a = 1.0; }
    if (self->a < 0) { self->a = 0.0; }
}

/* -------- FLCOLOR METHODS -------- */
/* -------- COLOR METHODS -------- */
/* Initialization */
void Color_dealloc(Color *self) {
    self->ob_type->tp_free((PyObject *)self);
}

PyObject *Color_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
    Color *self;
    self = (Color *)type->tp_alloc(type, 0);

    if (self != NULL) {
        self->r = 1.0;
        self->g = 1.0;
        self->b = 1.0;
        self->a = 1.0;
    }

    return (PyObject *)self;
}

int Color_init(Color *self, PyObject *args) {
    if (PySequence_Length(args) == 0) {
        return 0;
    }
    PyObject *firstarg = PySequence_GetItem(args, 0);
    if (PySequence_Check(firstarg) == 1) {
        Py_DECREF(args);
        args = firstarg;
    }
    
    if (!PyArg_ParseTuple(args, "|dddd", &self->r, &self->g, &self->b, &self->a)) {
        PyErr_Format(PyExc_TypeError, "values must be numeric.");
        return -1;
    }
    
    Color_SetBounds(self);
    
    return 0;
}

PyObject *Color_reduce(Color *self) {
    return Py_BuildValue("(O(dddd))", self->ob_type, self->r, self->g, self->b, self->a);
}

/* Presentation */
PyObject *Color_repr(Color *self) {
    char string[64];
    sprintf(string, "<flcolor.Color(%f, %f, %f, %f)>", self->r, self->g, self->b, self->a);
    return PyString_FromString(string);
}

PyObject *Color_str(Color *self) {
    return Color_repr(self);
}

PyObject *Color_richcompare(PyObject *a, PyObject *b, int op) {
    Color *c1, *c2;
    if (!PyObject_TypeCheck(a, &Color_Type)) {
        c1 = (Color *)PyObject_CallFunction((PyObject *)&Color_Type, "O", a);
        if (c1 == NULL) {
            PyErr_Format(PyExc_TypeError, "unsupported comparison for '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
            return NULL;
        }
    } else {
        Py_INCREF(a);
        c1 = (Color *)a;
    }
    
    if (!PyObject_TypeCheck(b, &Color_Type)) {
        c2 = (Color *)PyObject_CallFunction((PyObject *)&Color_Type, "O", b);
        if (c2 == NULL) {
            PyErr_Format(PyExc_TypeError, "unsupported comparison for '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
            return NULL;
        }
    } else {
        Py_INCREF(b);
        c2 = (Color *)b;
    }

    switch (op) {
        case Py_EQ:
            if ((c1->r == c2->r) && (c1->g == c2->g) && (c1->b == c2->b) && (c1->a == c2->a)) {
                Py_DECREF(c1);
                Py_DECREF(c2);
                return Py_True;
            }
            Py_DECREF(c1);
            Py_DECREF(c2);
            return Py_False;

        case Py_NE:
            if ((c1->r != c2->r) || (c1->g != c2->g) || (c1->b != c2->b) || (c1->a != c2->a)) {
                Py_DECREF(c1);
                Py_DECREF(c2);
                return Py_True;
            }
            Py_DECREF(c1);
            Py_DECREF(c2);
            return Py_False;
        default:
            Py_DECREF(c1);
            Py_DECREF(c2);
            return Py_NotImplemented;
    }
}

/* Sequence Operations */
Py_ssize_t Color_len(Color *self) {
    return 4;
}

PyObject *Color_item(Color *self, Py_ssize_t i) {
    if (i == 0)
        return PyFloat_FromDouble(self->r);
    if (i == 1)
        return PyFloat_FromDouble(self->g);
    if (i == 2)
        return PyFloat_FromDouble(self->b);
    if (i == 3)
        return PyFloat_FromDouble(self->a);

    PyErr_SetString(PyExc_IndexError, "Color index out of range");
    return NULL;
}

int Color_ass_item(Color *self, Py_ssize_t i, PyObject *value) {
    double val;
    if (!PyNumber_Check(value)) {
        PyErr_SetString(PyExc_ValueError, "Color assignment must be numerical");
        return -1;
    }

    val = PyFloat_AsDouble(value);

    if (i == 0) {
        self->r = val;
        Color_SetBounds(self);
        return 0;
    }
    if (i == 1) {
        self->g = val;
        Color_SetBounds(self);
        return 0;
    }
    if (i == 2) {
        self->b = val;
        Color_SetBounds(self);
        return 0;
    }
    if (i == 3) {
        self->a = val;
        Color_SetBounds(self);
        return 0;
    }

    PyErr_SetString(PyExc_IndexError, "Color index out of range");
    return -1;
}

/* Generic Number Operations (Scalar Only) */
Color *Color_oper(Color *a, double b, int oper) {
    /* Build Initial Values */
    Color *c = uNewColor(1, 1, 1, 1);

    /* Perform Operations */
    switch (oper) {
        case ADD:
            c->r = a->r + b;
            c->g = a->g + b;
            c->b = a->b + b;
            c->a = a->a + b;
            break;
        case SUB:
            c->r = a->r - b;
            c->g = a->g - b;
            c->b = a->b - b;
            c->a = a->a - b;
            break;
        case MUL:
            c->r = a->r * b;
            c->g = a->g * b;
            c->b = a->b * b;
            c->a = a->a * b;
            break;
        case DIV:
            c->r = a->r / b;
            c->g = a->g / b;
            c->b = a->b / b;
            c->a = a->a / b;
            break;
        case FLR:
            c->r = floor(a->r / b);
            c->g = floor(a->g / b);
            c->r = floor(a->b / b);
            c->g = floor(a->a / b);
            break;
        case MOD:
            c->r = fmod(a->r, b);
            c->g = fmod(a->g, b);
            c->b = fmod(a->b, b);
            c->a = fmod(a->a, b);
            break;
        case POW:
            c->r = pow(a->r, b);
            c->g = pow(a->g, b);
            c->b = pow(a->b, b);
            c->a = pow(a->a, b);
            break;
    }
    Color_SetBounds(c);
    return c;
}

void Color_ioper(Color *a, double b, int oper) {

    /* Perform Operations */
    switch (oper) {
        case ADD:
            a->r = a->r + b;
            a->g = a->g + b;
            a->b = a->b + b;
            a->a = a->a + b;
            break;
        case SUB:
            a->r = a->r - b;
            a->g = a->g - b;
            a->b = a->b - b;
            a->a = a->a - b;
            break;
        case MUL:
            a->r = a->r * b;
            a->g = a->g * b;
            a->b = a->b * b;
            a->a = a->a * b;
            break;
        case DIV:
            a->r = a->r / b;
            a->g = a->g / b;
            a->b = a->b / b;
            a->a = a->a / b;
            break;
        case FLR:
            a->r = floor(a->r / b);
            a->g = floor(a->g / b);
            a->r = floor(a->b / b);
            a->g = floor(a->a / b);
            break;
        case MOD:
            a->r = fmod(a->r, b);
            a->g = fmod(a->g, b);
            a->b = fmod(a->b, b);
            a->a = fmod(a->a, b);
            break;
        case POW:
            a->r = pow(a->r, b);
            a->g = pow(a->g, b);
            a->b = pow(a->b, b);
            a->a = pow(a->a, b);
            break;
    }
    Color_SetBounds(a);
}

/* Arithmetic Operations */
PyObject *Color_add(PyObject *a, PyObject *b) {
    Color *r;
    double d;
    if (!PyObject_TypeCheck(a, &Color_Type) || !PyNumber_Check(b)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for +: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    d = PyFloat_AsDouble(b);
    r = Color_oper((Color *)a, d, ADD);
    return (PyObject *)r;
}

PyObject *Color_iadd(Color *a, PyObject *b) {
    double d;
    if (!PyNumber_Check(b)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for +: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    d = PyFloat_AsDouble(b);
    Color_ioper(a, d, ADD);
    Py_INCREF(a);
    return (PyObject *)a;
}

PyObject *Color_sub(PyObject *a, PyObject *b) {
    Color *r;
    double d;
    if (!PyObject_TypeCheck(a, &Color_Type) || !PyNumber_Check(b)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for -: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    d = PyFloat_AsDouble(b);
    r = Color_oper((Color *)a, d, SUB);
    return (PyObject *)r;
}

PyObject *Color_isub(Color *a, PyObject *b) {
    double d;
    if (!PyNumber_Check(b)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for -: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    d = PyFloat_AsDouble(b);
    Color_ioper(a, d, SUB);
    Py_INCREF(a);
    return (PyObject *)a;
}

PyObject *Color_mul(PyObject *a, PyObject *b) {
    Color *r;
    double d;
    if (!PyObject_TypeCheck(a, &Color_Type) || !PyNumber_Check(b)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for *: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    d = PyFloat_AsDouble(b);
    r = Color_oper((Color *)a, d, MUL);
    return (PyObject *)r;
}

PyObject *Color_imul(Color *a, PyObject *b) {
    double d;
    if (!PyNumber_Check(b)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for *: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    d = PyFloat_AsDouble(b);
    Color_ioper(a, d, MUL);
    Py_INCREF(a);
    return (PyObject *)a;
}

PyObject *Color_div(PyObject *a, PyObject *b) {
    Color *r;
    double d;
    if (!PyObject_TypeCheck(a, &Color_Type) || !PyNumber_Check(b)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for /: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    d = PyFloat_AsDouble(b);
    r = Color_oper((Color *)a, d, DIV);
    return (PyObject *)r;
}

PyObject *Color_idiv(Color *a, PyObject *b) {
    double d;
    if (!PyNumber_Check(b)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for /: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    d = PyFloat_AsDouble(b);
    Color_ioper(a, d, DIV);
    Py_INCREF(a);
    return (PyObject *)a;
}

PyObject *Color_flr(PyObject *a, PyObject *b) {
    Color *r;
    double d;
    if (!PyObject_TypeCheck(a, &Color_Type) || !PyNumber_Check(b)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for //: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    d = PyFloat_AsDouble(b);
    r = Color_oper((Color *)a, d, FLR);
    return (PyObject *)r;
}

PyObject *Color_iflr(Color *a, PyObject *b) {
    double d;
    if (!PyNumber_Check(b)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for //: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    d = PyFloat_AsDouble(b);
    Color_ioper(a, d, DIV);
    Py_INCREF(a);
    return (PyObject *)a;
}

PyObject *Color_mod(PyObject *a, PyObject *b) {
    Color *r;
    double d;
    if (!PyObject_TypeCheck(a, &Color_Type) || !PyNumber_Check(b)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for %%: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    d = PyFloat_AsDouble(b);
    r = Color_oper((Color *)a, d, MOD);
    return (PyObject *)r;
}

PyObject *Color_imod(Color *a, PyObject *b) {
    double d;
    if (!PyNumber_Check(b)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for %%: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    d = PyFloat_AsDouble(b);
    Color_ioper(a, d, MOD);
    Py_INCREF(a);
    return (PyObject *)a;
}

PyObject *Color_pow(PyObject *a, PyObject *b) {
    Color *r;
    double d;
    if (!PyObject_TypeCheck(a, &Color_Type) || !PyNumber_Check(b)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for **: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    d = PyFloat_AsDouble(b);
    r = Color_oper((Color *)a, d, POW);
    return (PyObject *)r;
}

PyObject *Color_ipow(Color *a, PyObject *b) {
    double d;
    if (!PyNumber_Check(b)) {
        PyErr_Format(PyExc_TypeError, "unsupported operand type(s) for **: '%s' and '%s'",
                     a->ob_type->tp_name, b->ob_type->tp_name);
        return NULL;
    }
    d = PyFloat_AsDouble(b);
    Color_ioper(a, d, POW);
    Py_INCREF(a);
    return (PyObject *)a;
}

/* General Functions */
PyObject *Color_blend(Color *self, PyObject *args) {
    Py_RETURN_NONE;
}

/* Properties */
PyObject *Color_getr(Color *self, void *closure) {
    return Py_BuildValue("d", self->r);
}

int Color_setr(Color *self, PyObject *value, void *closure) {
    if (!PyNumber_Check(value)) {
        PyErr_Format(PyExc_TypeError, "value must be numeric.");
        return -1;
    }
    self->r = PyFloat_AsDouble(value);
    Color_SetBounds(self);
    return 0;
}

PyObject *Color_getg(Color *self, void *closure) {
    return Py_BuildValue("d", self->g);
}

int Color_setg(Color *self, PyObject *value, void *closure) {
    if (!PyNumber_Check(value)) {
        PyErr_Format(PyExc_TypeError, "value must be numeric.");
        return -1;
    }
    self->g = PyFloat_AsDouble(value);
    return 0;
}

PyObject *Color_getb(Color *self, void *closure) {
    return Py_BuildValue("d", self->b);
}

int Color_setb(Color *self, PyObject *value, void *closure) {
    if (!PyNumber_Check(value)) {
        PyErr_Format(PyExc_TypeError, "value must be numeric.");
        return -1;
    }
    self->b = PyFloat_AsDouble(value);
    return 0;
}

PyObject *Color_geta(Color *self, void *closure) {
    return Py_BuildValue("d", self->a);
}

int Color_seta(Color *self, PyObject *value, void *closure) {
    if (!PyNumber_Check(value)) {
        PyErr_Format(PyExc_TypeError, "value must be numeric.");
        return -1;
    }
    self->a = PyFloat_AsDouble(value);
    return 0;
}


/* Method Declaration */
PySequenceMethods Color_SequenceMethods = {
    (lenfunc)Color_len,                         /* sq_length */
    0,                                          /* sq_concat */
    0,                                          /* sq_repeat */
    (ssizeargfunc)Color_item,                   /* sq_item */
    0,
    (ssizeobjargproc)Color_ass_item,            /* sq_ass_item */
    0,                                          /* sq_contains */
    0,                                          /* sq_inplace_concat */
    0,                                          /* sq_inplace_repeat */
};

PyNumberMethods Color_NumberMethods = {
    (binaryfunc)Color_add,                      /* nb_add */
    (binaryfunc)Color_sub,                      /* nb_subtract */
    (binaryfunc)Color_mul,                      /* nb_multiply */
    (binaryfunc)Color_div,                      /* nb_divide */
    (binaryfunc)Color_mod,                      /* nb_remainder */
    0,                                          /* nb_divmod */
    (ternaryfunc)Color_pow,                     /* nb_power */
    0,                                          /* nb_negative */
    0,                                          /* nb_positive */
    0,                                          /* nb_absolute */
    0,                                          /* nb_nonzero */
    0,                                          /* nb_invert */
    0,                                          /* nb_lshift */
    0,                                          /* nb_rshift */
    0,                                          /* nb_and */
    0,                                          /* nb_xor */
    0,                                          /* nb_or */
    0,                                          /* nb_coerce */
    0,                                          /* nb_int */
    0,                                          /* nb_long */
    0,                                          /* nb_float */
    0,                                          /* nb_oct */
    0,                                          /* nb_hex */
    (binaryfunc)Color_iadd,                 /* nb_inplace_add */
    (binaryfunc)Color_isub,                 /* nb_inplace_subtract */
    (binaryfunc)Color_imul,                 /* nb_inplace_multiply */
    (binaryfunc)Color_idiv,                 /* nb_inplace_divide */
    (binaryfunc)Color_imod,                 /* nb_inplace_remainder */
    (ternaryfunc)Color_ipow,                /* nb_inplace_power */
    0,                                      /* nb_inplace_lshift */
    0,                                      /* nb_inplace_rshift */
    0,                                      /* nb_inplace_and */
    0,                                      /* nb_inplace_xor */
    0,                                      /* nb_inplace_or */
    (binaryfunc)Color_flr,                      /* nb_floor_divide */
    (binaryfunc)Color_div,                      /* nb_true_divide */
    (binaryfunc)Color_iflr,                     /* nb_inplace_floor_divide */
    (binaryfunc)Color_idiv,                     /* nb_inplace_true_divide */
    0,                                      /* nb_index */
};

PyMethodDef Color_methods[] = {
    {"blend", (PyCFunction)Color_blend, METH_VARARGS, NULL},
    {"__reduce__", (PyCFunction)Color_reduce, METH_NOARGS, NULL},
    {NULL}
};

PyGetSetDef Color_getsets[] = {
    {"r", (getter)Color_getr, (setter)Color_setr, NULL, NULL},
    {"g", (getter)Color_getg, (setter)Color_setg, NULL, NULL},
    {"b", (getter)Color_getb, (setter)Color_setb, NULL, NULL},
    {"a", (getter)Color_geta, (setter)Color_seta, NULL, NULL},
    {NULL}
};

/* -------- TYPE DECLARATIONS -------- */
PyTypeObject Color_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                                          /* ob_size */
    "flamingo.flcolor.Color",                   /* tp_name */
    sizeof(Color),                              /* tp_basicsize */
    0,                                          /* tp_itemsize */
    (destructor)Color_dealloc,                  /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_compare */
    (reprfunc)Color_repr,                       /* tp_repr */
    &Color_NumberMethods,                       /* tp_as_number */
    &Color_SequenceMethods,                     /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    (reprfunc)Color_str,                        /* tp_str */
    0,                                          /*i tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES,   /* tp_flags */
    0,                                          /* tp_doc */
    0,                                      /* tp_traverse */
    0,                                      /* tp_clear */
    (richcmpfunc)Color_richcompare,         /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    Color_methods,                              /* tp_methods */
    0,                                          /* tp_members */
    Color_getsets,                              /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)Color_init,                       /* tp_init */
    0,                                          /* tp_alloc */
    Color_new,                                  /* tp_new */
};

/* -------- MODULE INIT -------- */
PyMethodDef flcolor_methods[] = {
    {NULL}
};

PyMODINIT_FUNC
initflcolor(void)
{
    PyObject *m;
    if (PyType_Ready(&Color_Type) < 0)
        return;

    m = Py_InitModule3("flcolor", flcolor_methods, NULL);
    if (m == NULL)
        return;

    Py_INCREF(&Color_Type);
    PyModule_AddObject(m, "Color", (PyObject *)&Color_Type);
}
